use thiserror::Error;
use std::fmt;
use std::fmt::Display;

pub type Result<T> = std::result::Result<T, AppError>;


#[derive(Debug, Error)]
#[error("{message}:({code}")]
pub struct CodeMessageError {
    code: u32,
    message: String,
}
impl CodeMessageError {
    pub fn new(code: u32, message: &str) -> Self {
        Self { 
            code, 
            message:message.to_owned(),
        }
    }
}


#[derive(Debug, Error)]
#[error("{message}:({kind})")]
pub struct KindMessageError<K> 
where K: Display
{
    kind: K,
    message: String,
}
impl<K> KindMessageError<K> 
where K: Display
{
    pub fn new(kind: K, message: &str) -> Self {
        Self {
            kind,
            message: message.to_owned(),
        }
    }
}
#[derive(Debug)]
pub enum AppKind {
    OK = 0,
    NotExistLineFeed = 1, 
}
impl Display for AppKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OK => write!(f, "0"),
            Self::NotExistLineFeed => write!(f, "1"),
        }
    }
}




#[derive(Debug, Error)]
pub enum AppError {
    #[error("{}:({})", .0.message, .0.kind)]
    KindMessage(#[from]KindMessageError<AppKind>),

    #[error("{}:({})", .0.message, .0.code)]
    CodeMessage(#[from]CodeMessageError),
    #[error("I/O error occurred:{}", .0)]    
    Io(#[from]std::io::Error),
}
impl AppError {
    pub fn new_code(code: u32, message: &str) -> Self{
        AppError::CodeMessage(CodeMessageError::new(code, message))
    }
    pub fn new_io(err: std::io::Error) -> Self{
        AppError::Io(err)
    }
    pub fn new_kind(kind: AppKind, message: &str) -> Self {
        AppError::KindMessage(KindMessageError::new(kind, message))
    }
}
#[cfg(test)]
mod test {


    use super::*;
    use std::io::{Error, ErrorKind};
    #[test]
    fn use_code_message_error(){
        let e = AppError::new_code(1, "hello");
        println!("{e}");

        let e = AppError::new_io(Error::from(ErrorKind::AddrInUse));
        println!("{e}");

        let e = AppError::new_kind(AppKind::OK, "Ok");
        println!("{e}");

    }

    fn code_message_error() -> std::result::Result<(), CodeMessageError> {
        Err(CodeMessageError::new(12, "custom error msg"))
    }
    fn other_error_cvt_to_app_error()-> crate::error::Result<()> {
        let _ = code_message_error()?;
        let _file = std::fs::File::open("hell.txt")?;
        Ok(())
    }

    #[test]
    fn use_custome_error_with_result(){
        let r = other_error_cvt_to_app_error();
        println!("{}", r.err().unwrap());
    }

}